Analizando Vectores de Word2Vec
eduardofv / github
Objetivo
Word2Vec es un algoritmo de Procesamiento de Lenguaje Natural que genera representaciones de palabras en un espacio vectorial a partir de un corpus de texto. Para ello usa el “contexto” de cada palabra definido como el conjunto de n palabras alrededor de la palabra de interés. Estas representaciones se generan por medio de un modelo predictivo en el cual una red neuronal trata de predecir qué palabras aparecerán junto a una palabra determinada (modelo skipgram) o qué palabra es la aparecerá dadas otras n (modelo CBOW o Continous Bag Of World). Ver la publicación original. Se ha encontrado que estos vectores guardan ciertas relaciones semánticas y sintácticas que harían posible su uso en varias aplicaciones.
Es por esto que se vuelve necesario desarrollar una metodología para el análisis de estos vectores y que permitan crear aplicaciones de ellos. A continuación presento algunas ideas de qué se puede hacer con ellos.
El corpus y generación de embeddings.
La base para la generación de las representaciones vectoriales de palabras es un corpus de texto. Para el caso de español he utilizado el magnífico corpus recabado por Cristian Cardellino “Spanish Billion Words Corpus and Embeddings” (ver referencias) que consta de más de 1,000 millones de palabras en español. Cristian ha puesto disponible no solo el corpus ya limpio sino también los embeddings ya generados. Desafortunadamente no se encuentra el vocabulario y las frecuencias de palabras.
Para tener el vocabulario incluyendo frecuencias y un espacio de menos dimensiones corrí Word2Vec en Tensorflow en su versión optimizada con algunas modificaciones (github). Para esto se limpiaron los datos del corpus SBWE original eliminando acentos, caracteres especiales y convirtiendo a minúsculas. Se entrenó durante 15 épocas para 200 dimensiones. Se generaron embeddings para 808,854 términos con frecuencia de al menos 5 y el término UNK.
Carga de datos
#Funciones de análisis
#https://haozhu233.github.io/kableExtra/awesome_table_in_html.html#installation
library(kableExtra)
source("word2vec_analysis_functions.R")
set.seed(1234)
#Corpus 1: SBWE (español) corpus a partir de embeddings generados por w2v en TF
# Este corpus contiene 808,854 términos en 200 dimensiones, entrenado en 15 épocas
#d <- cargar.embeddings("data/full/ds-spanish/spanish_billion_words/")
# Usar datos previamente guardados como binarios, es mas rápido
d <- cargar.embeddings.rds("data/SBW-w2v-200d-")
#Corpus 2: SBWE (español) reducido a los primeros 100,000 elementos, para pruebas (mas rápido)
#d <- cargar.embeddings.rds("data/SBW-min-")
#Corpus 3: text8 (english) corpus a partir de embeddings generados por w2v en TF
#d <- cargar.embeddings.rds("data/text8-")
#d <- cargar.embeddings.rds("data/jobs_big1-")
emb <- d$emb
vocab <- d$vocab
rm(d)
Análisis descriptivo del espacio de componentes principales
Se efectúa un análisis de componentes principales (PCA). Esto nos permite transformar los vectores del espacio original a un espacio donde cada una de las dimensiones tiene una varianza progresivamente menor. Esto ayuda a identificar mejor dónde se pueden localizar algunos grupos de términos relacionados entre sí.
pca <- prcomp(emb)
plot(pca,type="l",col="red",main="Variance of the first principal components")

En la siguiente gráfica podemos observar la varianza acumulada por dimensión del espacio transformado. La recta azul muestra con cuántas dimensiones se alcanza el 50% de la varianza. La línea roja muestra cuánta varianza es explicada con las primeras 50 dimensiones.
pca.sum <- summary(pca)
plot(pca.sum$importance["Cumulative Proportion",],type="l",
main="Accumulated Variance along principal components",
xlab="Dimensions",ylab="Cumulative Proportion")
abline(v=50,col="red")
abline(h=0.5,col="blue")

Términos cercanos en el espacio original y en el espacio de componentes principales
Observamos términos cercanos para algunos casos de ejemplo. La cercanía se calcula aplicando Similitud Coseno del vector de un término con todos los demás. Observamos que hay diferencias importantes entre las similitudes encontradas en el espacio original y el espacio transformado por PCA. Si este último se normaliza no hay diferencia.
# Espacio transformado en componentes principales
pc <- as.data.frame(pca$x)
# Espacio transformado en componentes principales y normalizado
pcn <- normalize(pc)
dslist <- list(emb,pc,pcn)
dsnames <- c("original","principal components", "PC norm" )
wlist <- list("rey","yoga","mexico")
for(w in wlist){
i<-1
cat(paste0("### Terminos similares a '",w,"'\n\n"))
for(ds in dslist){
cat(paste0("#### Espacio ",dsnames[i],"\n\n"))
print(knitr::kable(similares(w,x=ds)[1:5,c("word","freq","cos.sim")],format = "html")
%>% kable_styling(bootstrap_options = "striped", full_width = F))
i<-i+1
}
}
Terminos similares a ‘rey’
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
2073
|
principe
|
64544
|
0.7517519
|
|
24759
|
pretendiente
|
2384
|
0.7487800
|
|
4503
|
trono
|
26113
|
0.7418567
|
|
4990
|
reinado
|
22950
|
0.7404978
|
|
2922
|
emperador
|
44152
|
0.7222650
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
4990
|
reinado
|
22950
|
0.5787233
|
|
4503
|
trono
|
26113
|
0.5689673
|
|
2073
|
principe
|
64544
|
0.5636508
|
|
24759
|
pretendiente
|
2384
|
0.5595046
|
|
2922
|
emperador
|
44152
|
0.5198701
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
4990
|
reinado
|
22950
|
0.5787233
|
|
4503
|
trono
|
26113
|
0.5689673
|
|
2073
|
principe
|
64544
|
0.5636508
|
|
24759
|
pretendiente
|
2384
|
0.5595046
|
|
2922
|
emperador
|
44152
|
0.5198701
|
Terminos similares a ‘yoga’
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
86310
|
tantra
|
273
|
0.8110373
|
|
666681
|
viniasa
|
6
|
0.8038652
|
|
25162
|
zen
|
2327
|
0.7912491
|
|
678710
|
satyananda
|
6
|
0.7909724
|
|
670712
|
devanand
|
6
|
0.7890270
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
86310
|
tantra
|
273
|
0.6154571
|
|
129021
|
hatha
|
130
|
0.6006844
|
|
24576
|
guru
|
2414
|
0.5690564
|
|
25162
|
zen
|
2327
|
0.5681425
|
|
666681
|
viniasa
|
6
|
0.5671910
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
86310
|
tantra
|
273
|
0.6154571
|
|
129021
|
hatha
|
130
|
0.6006844
|
|
24576
|
guru
|
2414
|
0.5690564
|
|
25162
|
zen
|
2327
|
0.5681425
|
|
666681
|
viniasa
|
6
|
0.5671910
|
Terminos similares a ‘mexico’
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
1610
|
monterrey
|
84007
|
0.8246961
|
|
4477
|
veracruz
|
26286
|
0.8241511
|
|
1116
|
guatemala
|
119202
|
0.8214685
|
|
4086
|
jalisco
|
29463
|
0.8201476
|
|
2356
|
sinaloa
|
55734
|
0.8161345
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
4477
|
veracruz
|
26286
|
0.6542324
|
|
1116
|
guatemala
|
119202
|
0.6525773
|
|
6179
|
chihuahua
|
17620
|
0.6495564
|
|
4086
|
jalisco
|
29463
|
0.6487193
|
|
1610
|
monterrey
|
84007
|
0.6403927
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
4477
|
veracruz
|
26286
|
0.6542324
|
|
1116
|
guatemala
|
119202
|
0.6525773
|
|
6179
|
chihuahua
|
17620
|
0.6495564
|
|
4086
|
jalisco
|
29463
|
0.6487193
|
|
1610
|
monterrey
|
84007
|
0.6403927
|
Analogías en el espacio original y en el de componentes principales
Observamos que se comportan las analogías en cada uno de los espacios. En este casi sí hay diferencias en los resultados. En general he observado mejores resultados en el espacio de componentes principales normalizado. Para términos comunes tambien se observa que hay mejores resultados si además se eliminan términos menos frecuentes del dataset.
anlist <- list(c("francia","paris","espana"),
c("rey","reina","hombre"),
c("apple","ios","microsoft"))
for(a in anlist){
cat(paste0("### Analogias para ",paste(a,collapse = "/"),"\n\n"))
i<-1
for(d in dslist){
cat(paste0("#### Espacio ",dsnames[i],"\n\n"))
print(knitr::kable(analogia(a[1],a[2],a[3],x=d)[1:3,c("word","freq","cos.sim")],
format = "html") %>%
kable_styling(bootstrap_options = "striped", full_width = F))
i<-i+1
}
}
Analogias para francia/paris/espana
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
345
|
madrid
|
330703
|
0.6552825
|
|
9779
|
gijon
|
9563
|
0.6524968
|
|
8741
|
vigo
|
11159
|
0.6480950
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
345
|
madrid
|
330703
|
0.4707165
|
|
9779
|
gijon
|
9563
|
0.4652975
|
|
8741
|
vigo
|
11159
|
0.4543748
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
345
|
madrid
|
330703
|
0.4710429
|
|
9779
|
gijon
|
9563
|
0.4549730
|
|
515
|
barcelona
|
234558
|
0.4412346
|
Analogias para rey/reina/hombre
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
32037
|
vendedora
|
1564
|
0.6300830
|
|
19304
|
betty
|
3551
|
0.6253540
|
|
16111
|
maravilla
|
4671
|
0.6243655
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
16606
|
anciana
|
4463
|
0.4212914
|
|
15359
|
senorita
|
5000
|
0.4164873
|
|
235
|
mujer
|
426507
|
0.3964051
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
32037
|
vendedora
|
1564
|
0.4445025
|
|
16606
|
anciana
|
4463
|
0.4282076
|
|
15359
|
senorita
|
5000
|
0.4267983
|
Analogias para apple/ios/microsoft
Espacio original
|
|
word
|
freq
|
cos.sim
|
|
4572
|
windows
|
25565
|
0.8004357
|
|
11924
|
android
|
7292
|
0.7930679
|
|
14971
|
xbox
|
5214
|
0.7612021
|
Espacio principal components
|
|
word
|
freq
|
cos.sim
|
|
4572
|
windows
|
25565
|
0.7661221
|
|
11924
|
android
|
7292
|
0.7598521
|
|
14971
|
xbox
|
5214
|
0.7170435
|
Espacio PC norm
|
|
word
|
freq
|
cos.sim
|
|
4572
|
windows
|
25565
|
0.7303656
|
|
11924
|
android
|
7292
|
0.7254232
|
|
14971
|
xbox
|
5214
|
0.6899744
|
Valores extremos en algunas dimensiones
Se observan grupos identificables en los valores extremos de cada una de las dimensiones. Especialmente en dimensiones con menor varianza, los extremos son grupos aparentemente mas definidos. En otras palabras, parece que en las dimensiones que explican menos varianza los conjuntos de términos en los extremos son mas definidos.
word.plot.dimension(pcn,target.dim = "PC1",sec.dim = "PC2",max.plot = 20)

word.plot.dimension(pcn,target.dim = "PC200",sec.dim = "PC1",max.plot = 20)

word.plot.dimension(pcn,target.dim = "PC199",sec.dim = "PC1",max.plot = 20)

Análisis de grupos a partir de un término
Encontramos una metodología simple para el análisis de los términos relacionados con un término base:
- Se obtienen un número determinado de términos mas cercanos (similares) a partir del espacio de componentes principales normalizado.
- Se hace un K-Means clustering de los términos. Opcionalmente se puede realizar previamente un análsis para determinar el número de clusters que se deben generar.
- Se grafican en las dos primeras dimensiones utilizadas.
- Se genera un wordcloud por cada cluster de palabras generado.
Podemos observar que en general los grupos identifican distintos grupos de palabras relacionados entre sí de alguna forma. Hemos visto grupos de adjetivos, razas de perros, características semánticas, etc. razonablemente bien agrupadas mediante este método.
termlist <- list("perro","web","algoritmo")
termclist <- c(8,7,8)
for(w in termlist){
cat(paste0("### Analisis para el termino: ",w,"\n\n"))
dclust<-pca.cluster.similar(w,x = pcn,v = vocab,n=300,show.center.analysis = T)
dclust<-pca.cluster.similar(w,x = pcn,v = vocab,n=300,centers=termclist[w==termlist])
cluster.wordcloud(dclust,scale=c(2.75,0.5))
cat("\n\n")
}
Links a los datos
Datos
Cristian Cardellino: Spanish Billion Words Corpus and Embeddings (March 2016), http://crscardellino.me/SBWCE/
Embeddings y vocabulario para Spanish Billion Words a partir de mi corrida de Word2Vec en Tensorflow: